/* Flow-IPC: SHM-jemalloc
 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
 * Each commit is copyright by its respective author or author's employer.
 *
 * Licensed under the MIT License:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE. */

#pragma once

#include "ipc/shm/arena_lend/detail/add_reference.hpp"
#include "ipc/shm/arena_lend/detail/shm_pool_offset_ptr_data.hpp"

namespace ipc::shm::arena_lend
{

/**
 * Fancy pointer containing information about an object in shared memory. In particular, it contains an offset
 * from the base of a shared memory pool. A process would generally need to have the shared memory pool, corresponding
 * to the shared memory pool id, mapped to access the object.
 *
 * @tparam Pointed_type The underlying type of the object being held.
 * @tparam Repository_type The shared memory pool repository type to convert a handle to a pointer. The requirement
 *                         is that the following static interfaces exist:
 *                         1. void* to_address(std::string_view, std::size_t)
 *                         2. bool from_address(void*, std::shared_ptr<Shm_pool>&, std::size_t)
 * @tparam Difference_type A signed integer type that can represent the arithmetic operations on the pointer.
 * @tparam CAN_STORE_RAW_PTR Whether a raw pointer can be stored if a pointer cannot be converted to a fancy
 *                               pointer; otherwise, an invalid fancy pointer would be stored that converts to nullptr.
 *
 * @todo - Move method definitions out of class declarations.
 */
template <typename Pointed_type,
          typename Repository_type,
          typename Difference_type,
          bool CAN_STORE_RAW_PTR>
class Shm_pool_offset_ptr
{
public:
  /// Alias for std::pointer_traits compliance.
  using element_type = Pointed_type;
  /// Alias for std::pointer_traits compliance.
  template <typename U>
  using rebind = Shm_pool_offset_ptr<U, Repository_type, Difference_type, CAN_STORE_RAW_PTR>;
  /// Alias for std::pointer_traits and std::iterator_traits compliances.
  using difference_type = Difference_type;
  /// Alias for std::iterator_traits compliance. Removes const and volatile traits.
  using value_type = typename std::remove_cv<Pointed_type>::type;
  /// Alias for std::iterator_traits compliance.
  using pointer = Pointed_type*;
  /// Alias to follow naming convention.
  using Pointer = pointer;
  /// Alias for std::iterator_traits compliance.
  using reference = typename detail::Add_reference<Pointed_type>::m_type;
  /// Alias to follow naming convention.
  using Reference = reference;
  /// Alias for std::iterator_traits compliance.
  using iterator_category = std::random_access_iterator_tag;

  /**
   * Default constructor.
   */
  Shm_pool_offset_ptr() = default;

  /**
   * Construction from raw pointer.
   *
   * @param p The pointer to an object or nullptr.
   */
  Shm_pool_offset_ptr(const void* p) :
    m_data(p)
  {
  }

  /**
   * Default copy constructor. (Seems this is specified explicitly, instead of just letting it be autogenerated
   * without mentioning it at all, because of the presence of the other almost-copy constructor taking a source
   * object of the same template but different concrete type.) (By rules of C++, we must specify it as taking a
   * `const` ref, as opposed to by value, so as to avoid infinite copy ctor recursion. The rest of the API
   * intentionally takes args by value, as in our case `sizeof(*this) == sizeof(void*)`, so the indirection of
   * references only slows things down. As for the copy ctor technically taking a ref shouldn't matter; the optimizer
   * will take care of it.)
   *
   * @param other The other offset pointer to copy from.
   */
  Shm_pool_offset_ptr(const Shm_pool_offset_ptr& other) = default;

/* gcc is pretty paranoid about the type-punning when initializing m_data below, but we know what we are doing.
 * The code appears solid, so let's bypass it temporarily. @todo Perhaps we can finagle-in a `union` to avoid
 * the warning? */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"

  /**
   * Construct from an offset pointer containing a different convertible type that will convert using the same address.
   * We copy the data directly when this is the case for a potential optimization.
   *
   * @tparam Other_type The underlying type of the object being held in the other offset pointer.
   * @tparam OTHER_CAN_STORE_RAW_POINTER Whether the other offset pointer can store a raw pointer.
   * @param other The other offset pointer to convert from.
   * @param unused_type_param Unused parameter affecting method selection.
   */
  template <typename Other_type, bool OTHER_CAN_STORE_RAW_POINTER>
  Shm_pool_offset_ptr(
    Shm_pool_offset_ptr<Other_type,
                        Repository_type,
                        Difference_type,
                        OTHER_CAN_STORE_RAW_POINTER> other,
    [[maybe_unused]] typename std::enable_if_t<std::is_convertible_v<Other_type*, Pointer> &&
                                               std::is_same_v<std::remove_cv_t<Other_type>,
                                                              std::remove_cv_t<Pointed_type>>>* = nullptr) :
    // We convert the data to the current class to access the private variable and then convert it back
    m_data(*reinterpret_cast<
             const detail::Shm_pool_offset_ptr_data<
               Repository_type,
               OTHER_CAN_STORE_RAW_POINTER>*>(&(reinterpret_cast<const Shm_pool_offset_ptr&>(other).m_data)))
  {
  }

  /**
   * Default copy assignment.  Without declaring it explicitly, some compilers (at least clang-17)
   * warn, because of the presence of the auto-generated copy-ctor above.
   *
   * @param other The other offset pointer to copy from.
   * @return `*this`.
   */
  Shm_pool_offset_ptr& operator=(const Shm_pool_offset_ptr& other) = default;

#pragma GCC diagnostic pop // See above.

  /**
   * Construct from an offset pointer containing a different convertible type that will convert using a potentially
   * different address. We convert from the translated pointer when this is the case.
   *
   * @tparam Other_type The underlying type of the object being held in the other pointer.
   * @tparam OTHER_CAN_STORE_RAW_POINTER Whether the other pointer can store a raw pointer.
   * @param other The other offset pointer to convert from.
   * @param unused_type_param Unused parameter affecting method selection.
   */
  template <typename Other_type, bool OTHER_CAN_STORE_RAW_POINTER>
  Shm_pool_offset_ptr(
    const Shm_pool_offset_ptr<Other_type,
                              Repository_type,
                              Difference_type,
                              OTHER_CAN_STORE_RAW_POINTER>& other,
    [[maybe_unused]] typename std::enable_if_t<std::is_convertible_v<Other_type*, Pointer> &&
                                               !std::is_same_v<std::remove_cv_t<Other_type>,
                                                               std::remove_cv_t<Pointed_type>>>* = nullptr) :
    // We use the pointer stored and construct from that as the object address may be different
    Shm_pool_offset_ptr(static_cast<Pointer>(other.get()))
  {
  }

  /**
   * Returns whether the pointer being held is a non-null, raw pointer.
   *
   * @return See above.
   */
  bool is_raw() const
  {
    return m_data.is_raw();
  }

  /**
   * Returns whether the pointer being held is a non-null, offset-expressed pointer.
   *
   * @return See above.
   */
  bool is_offset() const
  {
    return (!is_raw()) && bool(*this);
  }

  /**
   * Returns an instance of this class type referencing an object. This satisfies the std::pointer_traits
   * interface.
   *
   * @param object The object to reference.
   *
   * @return See above.
   */
  static Shm_pool_offset_ptr pointer_to(Reference object)
  {
    return Shm_pool_offset_ptr(&object);
  }

  /**
   * Retrieves the pointer to the object, if there is one stored; otherwise, nullptr.
   *
   * @return See above.
   */
  Pointer get() const
  {
    return static_cast<Pointer>(m_data.get());
  }
  /**
   * Retrieves the pointer to the object, if there is one stored; otherwise, nullptr.
   *
   * @return See above.
   */
  Pointer operator->() const
  {
    return get();
  }
  /**
   * Retrieves a reference to the object, which must be stored or else behavior is undefined.
   *
   * @return See above.
   */
  Reference operator*() const
  {
    return *(get());
  }

  /**
   * Pointer subtraction operator.
   *
   * @param offset The amount of Pointed_type objects to decrement from the current pointer location.
   *
   * @return A reference to this instance.
   */
  Shm_pool_offset_ptr& operator-=(Difference_type offset)
  {
    return operator+=(-offset);
  }
  /**
   * Predecrement operator, which subtracts Pointed_type object size from the current pointer location.
   *
   * @return A reference to this instance.
   */
  Shm_pool_offset_ptr& operator--()
  {
    m_data.increment(-(static_cast<typename Data::diff_t>(sizeof(Pointed_type))));
    return *this;
  }
  /**
   * Post-decrement operator, which subtracts Pointed_type object size from the current pointer location,
   * and returns the current pointer location (prior to subtraction).
   *
   * @return A copy of this instance.
   */
  Shm_pool_offset_ptr operator--(int)
  {
    Shm_pool_offset_ptr tmp(*this);
    --*this;
    return tmp;
  }
  /**
   * Returns a copy of an offset pointer subtracted by an offset.
   *
   * @todo These `friend`s appear to only be `friend`s to be able to stuff the function bodies inside the class
   * body; this seems against the spirit of `friend`ship. Also the official coding convention is to avoid
   * function bodies inside class bodies anyway. (The reasoning for that admittedly less-concise convention
   * is to promote API and impl separation to the extent possible in C++; as well as for consistency.)
   *
   * @param left The offset pointer to copy and subtract from.
   * @param diff The amount of Pointed_type objects to decrement from the current pointer location.
   *
   * @return See above.
   */
  friend Shm_pool_offset_ptr operator-(Shm_pool_offset_ptr left, Difference_type diff)
  {
    return left -= diff;
  }
  /**
   * Returns the difference between one pointer and another.
   *
   * @param ptr1 The minuend of the subtraction.
   * @param ptr2 The subtrahend of the subtraction.
   *
   * @return See above.
   */
  friend Difference_type operator-(Shm_pool_offset_ptr ptr1, Shm_pool_offset_ptr ptr2)
  {
    return static_cast<Difference_type>(ptr1.get() - ptr2.get());
  }

  /**
   * Pointer addition operator.
   *
   * @param offset The amount of Pointed_type objects to increment to the current pointer location.
   *
   * @return A reference to this instance.
   */
  Shm_pool_offset_ptr& operator+=(Difference_type offset)
  {
    m_data.increment(static_cast<typename Data::diff_t>(offset)
                     * static_cast<typename Data::diff_t>(sizeof(Pointed_type)));
    return *this;
  }
  /**
   * Preincrement operator, which adds Pointed_type object size to the current pointer location.
   *
   * @return A reference to this instance.
   */
  Shm_pool_offset_ptr& operator++()
  {
    m_data.increment(static_cast<typename Data::diff_t>(sizeof(Pointed_type)));
    return *this;
  }
  /**
   * Post-increment operator, which adds Pointed_type object size to the current pointer location,
   * and returns the current pointer location (prior to addition).
   *
   * @return A copy of this instance.
   */
  Shm_pool_offset_ptr operator++(int)
  {
    Shm_pool_offset_ptr tmp(*this);
    ++*this;
    return tmp;
  }
  /**
   * Returns a copy of an offset pointer added by an offset.
   *
   * @param left The offset pointer to copy and add to.
   * @param diff The amount of Pointed_type objects to increment to the current pointer location.
   *
   * @return See above.
   */
  friend Shm_pool_offset_ptr operator+(Shm_pool_offset_ptr left, Difference_type diff)
  {
    return left += diff;
  }
  /**
   * Returns a copy of an offset pointer added by an offset.
   *
   * @param diff The amount of Pointed_type objects to increment to the current pointer location.
   * @param right The offset pointer to copy and add to.
   *
   * @return See above.
   */
  friend Shm_pool_offset_ptr operator+(Difference_type diff, Shm_pool_offset_ptr right)
  {
    return operator+(right, diff);
  }

  /**
   * Returns whether the offset pointer is holding an object.
   *
   * @return See above.
   */
  explicit operator bool() const
  {
    return bool(m_data);
  }
  /**
   * Returns whether the offset pointer is not holding an object.
   *
   * @return See above.
   */
  bool operator!() const
  {
    return !(operator bool());
  }
  /**
   * Compares two pointers for equality.
   *
   * @param ptr1 The first pointer to compare.
   * @param ptr2 The second pointer to compare.
   *
   * @return See above.
   */
  friend bool operator==(Shm_pool_offset_ptr ptr1, Shm_pool_offset_ptr ptr2)
  {
    return ptr1.get() == ptr2.get();
  }
  /**
   * Compares two pointers for inequality.
   *
   * @param ptr1 The first pointer to compare.
   * @param ptr2 The second pointer to compare.
   *
   * @return See above.
   */
  friend bool operator!=(Shm_pool_offset_ptr ptr1, Shm_pool_offset_ptr ptr2)
  {
    return !operator==(ptr1, ptr2);
  }
  /**
   * Compares two pointers to see if the first is less than the second.
   *
   * @param ptr1 The first pointer to compare.
   * @param ptr2 The second pointer to compare.
   *
   * @return See above.
   */
  friend bool operator<(Shm_pool_offset_ptr ptr1, Shm_pool_offset_ptr ptr2)
  {
    return ptr1.get() < ptr2.get();
  }
  /**
   * Compares two pointers to see if the first is less than or equal to the second.
   *
   * @param ptr1 The first pointer to compare.
   * @param ptr2 The second pointer to compare.
   *
   * @return See above.
   */
  friend bool operator<=(Shm_pool_offset_ptr ptr1, Shm_pool_offset_ptr ptr2)
  {
    return ptr1.get() <= ptr2.get();
  }
  /**
   * Compares two pointers to see if the first is greater than the second.
   *
   * @param ptr1 The first pointer to compare.
   * @param ptr2 The second pointer to compare.
   *
   * @return See above.
   */
  friend bool operator>(Shm_pool_offset_ptr ptr1, Shm_pool_offset_ptr ptr2)
  {
    return ptr1.get() > ptr2.get();
  }
  /**
   * Compares two pointers to see if the first is greater than or equal to the second.
   *
   * @param ptr1 The first pointer to compare.
   * @param ptr2 The second pointer to compare.
   *
   * @return See above.
   */
  friend bool operator>=(Shm_pool_offset_ptr ptr1, Shm_pool_offset_ptr ptr2)
  {
    return ptr1.get() >= ptr2.get();
  }

  /**
   * Indexing operator, which returns a reference to the index'th element.
   *
   * @param index Index into a sequenced container.
   *
   * @return See above.
   */
  Reference operator[](Difference_type index) const noexcept
  {
    return get()[index];
  }

private:
  using Data = detail::Shm_pool_offset_ptr_data<Repository_type, CAN_STORE_RAW_PTR>;
  Data m_data;
  static_assert(std::is_signed_v<Difference_type>, "Pointer difference type must be signed.");
  static_assert(sizeof(typename decltype(m_data)::diff_t) >= sizeof(Difference_type),
                "Due to internal requirements the pointer difference type cannot exceed a certain "
                  "(reasonably generous) bit width; for comparison note that boost::offset_ptr<> defaults to `int`.  "
                  "This requiremenet could be relaxed, in which case a problem would occur at run-time, but only "
                  "if a sufficient giant pointer difference is actually involved at some point.");
}; // class Shm_pool_offset_ptr

} // namespace ipc::shm::arena_lend
